package mobilefeel.touchscreen;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import mobilefeel.AndroidDevice;
import mobilefeel.DeviceDispatcher;
import mobilefeel.NotConnectedException;
import mobilefeel.dto.DataTransferObject;
import mobilefeel.dto.TouchEvent;
import mobilefeel.dto.ViewEvent;

/**
 * Represents an Android device's view. View allows the developer to manage
 * listeners of different types of view events and to obtain information from
 * the device's view. 
 */
public final class View {

	private int id;
	private String name;
	private int width;
	private int height;
	
	private AndroidDevice androidDevice;
	private List<OnTouchListener> onTouchListeners;
	private List<OnClickListener> onClickListeners;
	private List<OnLongClickListener> onLongClickListeners;
	
	View(AndroidDevice android){
		this.androidDevice = android;
		onTouchListeners = new ArrayList<OnTouchListener>();
		onClickListeners = new ArrayList<OnClickListener>();
		onLongClickListeners = new ArrayList<OnLongClickListener>();
		id = -1;
		name = "";
	}
	
	/**
	 * Returns the view's hexadecimal ID.
	 * 
	 * @return an integer containing the view's hexadecimal ID
	 */
	public int getId() {
		return id;
	}

	void setId(int id) {
		this.id = id;
	}

	/**
	 * Returns the Android device that owns the view. 
	 * 
	 * @return the corresponding {@link AndroidDevice} instance 
	 */
	public AndroidDevice getAndroidDevice() {
		return androidDevice;
	}
	
	/**
	 * Registers a callback to be invoked when an Android view performs a touch
	 * event.
	 * 
	 * @param listener the {@link OnTouchListener} that will be notified of
	 * touch events (cannot be null)
	 * @throws NotConnectedException if the device is not connected
	 * @throws IOException if a network error occurs while communicating with
	 * the device
	 * @see #removeOnTouchListener(OnTouchListener)
	 */
	public void addOnTouchListener(OnTouchListener listener) throws NotConnectedException, IOException {
		if (listener == null)
			throw new IllegalArgumentException("listener cannot be null.");
		if (onTouchListeners.size() == 0) {
			ViewEvent viewEvent = new ViewEvent();
			viewEvent.setType(DataTransferObject.TOUCHSCREEN_REGISTER_TOUCH_EVENT);
			viewEvent.setId(this.id);
			DeviceDispatcher.sendObject(androidDevice, viewEvent);
		}
		this.onTouchListeners.add(listener);
	}
	
	/**
	 * Unregisters a touch events listener.
	 * 
	 * @param listener the {@link OnTouchListener} that will not be notified
	 * of touch events anymore
	 * @throws NotConnectedException if the device is not connected
	 * @throws IOException if a network error occurs while communicating with
	 * the device
	 * @see #addOnTouchListener(OnTouchListener)
	 */
	public void removeOnTouchListener(OnTouchListener listener) throws NotConnectedException, IOException {
		this.onTouchListeners.remove(listener);
		if (onTouchListeners.size() == 0) {
			ViewEvent viewEvent = new ViewEvent();
			viewEvent.setType(DataTransferObject.TOUCHSCREEN_UNREGISTER_TOUCH_EVENT);
			viewEvent.setId(this.id);
			DeviceDispatcher.sendObject(androidDevice, viewEvent);
		}
	}
	
	/**
	 * Registers a callback to be invoked when an Android view performs a long
	 * click event (clicked and held).
	 * 
	 * @param listener the {@link OnLongClickListener} that will be notified of
	 * long click events (cannot be null)
	 * @throws NotConnectedException if the device is not connected
	 * @throws IOException if a network error occurs while communicating with
	 * the device
	 * @see #removeOnLongClickListener(OnLongClickListener)
	 */
	public void addOnLongClickListener(OnLongClickListener listener) throws NotConnectedException, IOException {
		if (listener == null)
			throw new IllegalArgumentException("listener cannot be null.");
		if (onLongClickListeners.size() == 0) {
			ViewEvent viewEvent = new ViewEvent();
			viewEvent.setType(DataTransferObject.TOUCHSCREEN_REGISTER_LONGCLICK_EVENT);
			viewEvent.setId(this.id);
			DeviceDispatcher.sendObject(androidDevice, viewEvent);
		}
		this.onLongClickListeners.add(listener);
	}
	
	/**
	 * Unregisters a long click events listener.
	 * 
	 * @param listener the {@link OnLongClickListener} that will not be
	 * notified of long click events anymore
	 * @throws NotConnectedException if the device is not connected
	 * @throws IOException if a network error occurs while communicating with
	 * the device
	 * @see #addOnLongClickListener(OnLongClickListener)
	 */
	public void removeOnLongClickListener(OnLongClickListener listener) throws NotConnectedException, IOException {
		this.onLongClickListeners.remove(listener);
		if (onLongClickListeners.size() == 0) {
			ViewEvent viewEvent = new ViewEvent();
			viewEvent.setType(DataTransferObject.TOUCHSCREEN_UNREGISTER_LONGCLICK_EVENT);
			viewEvent.setId(this.id);
			DeviceDispatcher.sendObject(androidDevice, viewEvent);
		}
	}
	
	/**
	 * Registers a callback to be invoked when an Android view performs a click
	 * event.
	 * 
	 * @param listener the {@link OnClickListener} that will be notified of
	 * click events (cannot be null)
	 * @throws NotConnectedException if the device is not connected
	 * @throws IOException if a network error occurs while communicating with
	 * the device
	 * @see #removeOnClickListener(OnClickListener)
	 */
	public void addOnClickListener(OnClickListener listener) throws NotConnectedException, IOException {
		if (listener == null)
			throw new IllegalArgumentException("listener cannot be null.");
		if (onClickListeners.size() == 0) {
			ViewEvent viewEvent = new ViewEvent();
			viewEvent.setType(DataTransferObject.TOUCHSCREEN_REGISTER_CLICK_EVENT);
			viewEvent.setId(this.id);
			DeviceDispatcher.sendObject(androidDevice, viewEvent);
		}
		this.onClickListeners.add(listener);
	}
	
	/**
	 * Unregisters a click events listener.
	 * 
	 * @param listener the {@link OnClickListener} that will not be notified of
	 * click events anymore
	 * @throws NotConnectedException if the device is not connected
	 * @throws IOException if a network error occurs while communicating with
	 * the device
	 * @see #addOnClickListener(OnClickListener)
	 */
	public void removeOnClickListener(OnClickListener listener) throws NotConnectedException, IOException {
		this.onClickListeners.remove(listener);
		if (onClickListeners.size() == 0) {
			ViewEvent viewEvent = new ViewEvent();
			viewEvent.setType(DataTransferObject.TOUCHSCREEN_UNREGISTER_CLICK_EVENT);
			viewEvent.setId(this.id);
			DeviceDispatcher.sendObject(androidDevice, viewEvent);
		}
	}

	synchronized void notifyOnTouchListeners(TouchEvent event) {
		for (OnTouchListener listener : this.onTouchListeners)
			listener.onTouch(this, event);
	}
	
	synchronized void notifyOnClickListeners() {
		for (OnClickListener listener : this.onClickListeners)
			listener.onClick(this);
	}
	
	synchronized void notifyOnLongClickListeners() {
		for (OnLongClickListener listener : this.onLongClickListeners)
			listener.onLongClick(this);
	}
	
	/**
	 * Returns the view's identification name.
	 * 
	 * @return a String containing the view's name
	 */
	public String getName() {
		return name;
	}

	void setName(String name) {
		this.name = name;
	}
	
	/**
	 * Returns the view's screen width.
	 * 
	 * @return an Integer representing the view's width
	 */
	public int getWidth() {
		return width;
	}

	void setWidth(int width) {
		this.width = width;
	}

	/**
	 * Returns the view's screen height.
	 * 
	 * @return an Integer representing the view's height
	 */
	public int getHeight() {
		return height;
	}

	void setHeight(int height) {
		this.height = height;
	}

	/**
	 * Returns a string containing a concise, human-readable description of
	 * this object. It is equivalent to the following expression: <br/>
	 * getName() + " (" + getId() + ")"
	 * 
	 * @return a printable representation of this object
	 */
	@Override
	public String toString() {
		return getName() + " (" + getId() + ")";
	}
	
}